package org.mentalsmash.tazio.transactions.objects;

import java.io.Serializable;
import java.lang.reflect.Proxy;
import java.util.Iterator;

import org.mentalsmash.documentation.Concurrency;
import org.mentalsmash.tazio.cache.Cache;
import org.mentalsmash.tazio.cache.CachedObject;
import org.mentalsmash.tazio.cache.Filter;
import org.mentalsmash.tazio.cache.NoSuchCachedObjectException;
import org.mentalsmash.tazio.cache.OIDAlreadyInUseException;
import org.mentalsmash.tazio.cache.filters.RootSetFilter;
import org.mentalsmash.tazio.cache.impl.CachingProxy;
import org.mentalsmash.tazio.commons.TazioRuntimeException;
import org.mentalsmash.tazio.commons.identifiers.OID;
import org.mentalsmash.tazio.transactions.RollbackException;
import org.mentalsmash.tazio.transactions.Share;
import org.mentalsmash.tazio.transactions.Transactional;
import org.mentalsmash.tazio.transactions.TransactionalInterfaceException;
import org.mentalsmash.tazio.transactions.TransactionalSystemRuntimeException;
import org.mentalsmash.tazio.transactions.management.TransactionStateException;
import org.mentalsmash.tazio.transactions.management.rm.RMTransactionsFacade;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;

/**
 * The RootSerWrapper wraps Tazio's internal root set and exposes the Share
 * interface to the user, using the RootSet as data source.
 * <p>
 * It is responsible of retrieving a root set instance at startup time
 * </p>
 * 
 * @version 0.0.1 19/nov/2009
 * @author Andrea Reale <andrea.reale@gmail.com>
 * 
 */
@Concurrency(concurrentAccess = true, specification = "every field is immutable")
class RootSetWrapper implements Share {

    private final static Logger log = LoggerFactory
	    .getLogger(RootSetWrapper.class);

    private final TxObjectWrapperFactory objectWrapperFactory;
    private RootSet rootSet = null;

    private final Cache localCache;

    @Inject
    RootSetWrapper(final TxObjectWrapperFactory factory,
	    final RMTransactionsFacade facade, final RootSet rs,
	    final Cache cache) {
	this.rootSet = rs;
	this.objectWrapperFactory = factory;
	this.localCache = cache;
	
	
	
	// registering a filter for the root set on the cache
	Filter rsFilter = new RootSetFilter(cache);
	cache.addFilter(rsFilter);

    }

    public RootSet getRootSet() {
	return this.rootSet;
    }

    // public synchronized void setRootSet(final RootSet rs) {
    // Preconditions.checkNotNull(rs, "RootSet must be not null");
    // this.rootSet = rs;
    // }

    @Override
    public <T extends Serializable> T createObject(
	    final Class<T> transactionalIface,
	    final String label)
	    throws TransactionStateException, RollbackException {
	
	Preconditions.checkNotNull(label);

	final boolean valid = checkTypeValidity(transactionalIface);
	if (!valid) {
	    throw new TransactionalInterfaceException(
		    "Interface {} is not proxable. Check the log for details");
	}

	final T proxiedInstance = createProxiedInstance(label, transactionalIface);

	try {
	    getRootSet().add(proxiedInstance);
	    
	} catch (Exception e) {
	    OID oid = ((TransactionalObject)proxiedInstance).getObjectIdentifier();
	    
	    log.error("Addition to RootSet didn't commit, removing object "+oid+" from the local cache");
	    
	    this.localCache.removeObject(oid);
	}

	return proxiedInstance;

    }

    /**
         * Given a suitable interface type (i.e. one who correctly implements
         * {@link Transactional} semantics), creates an instance of that
         * interface using the provided implementation.
         * 
         * @param <E>
         * 
         * @param txIface
         *                the interface to create a proxy for
         * @return an instance proxied with transactional logic
         */
    private <E> E createProxiedInstance(final String label, final Class<E> txIface) {
	Serializable instance = null;
	final TxObjectWrapper wrapper;
	final Class<?>[] interfaces; // set of interfaces for the proxy to
	// implement
	// retrieve the annotation. Already checked whether present or not.
	final Class<?> implementation = txIface.getAnnotation(
		Transactional.class).implementation();

	try {
	    instance = (Serializable) implementation.newInstance();

	} catch (final InstantiationException e) {
	    log.error("Fatal error while instantiating proxy", e);
	    throw new TransactionalSystemRuntimeException(e);
	} catch (final IllegalAccessException e) {
	    log.error("Fatal error while instantiating proxy", e);
	    throw new TransactionalSystemRuntimeException(e);
	}

	OID oid = OID.generateOID();
// try {
// this.localCache.addObject(oid, instance);
// } catch (OIDAlreadyInUseException e) {
// log.error("OID is broken!!", e);
// }
//
// try {
// instance = (Serializable) this.localCache.getObject(oid);
// } catch (NoSuchCachedObjectException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// } catch (ClassCastException e) {
// // TODO Auto-generated catch block
//
// e.printStackTrace();
// }

	wrapper = this.objectWrapperFactory.createTxWrapper(oid,label, instance);
	interfaces = new Class<?>[] { txIface, TransactionalObject.class };

	final E newProxyInstance = txIface.cast(Proxy.newProxyInstance(txIface
		.getClassLoader(), interfaces, wrapper));

	try {
	    this.localCache.addObject(oid, (Serializable)newProxyInstance);
	} catch (OIDAlreadyInUseException e) {
	    log.error("OID is broken!!", e);
	}
	
	E returnToClient = null;

	try {
	    returnToClient =  txIface.cast(this.localCache.getObject(oid));
	} catch (NoSuchCachedObjectException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	} catch (ClassCastException e) {
	    // TODO Auto-generated catch block

	    e.printStackTrace();
	}

	return returnToClient;
    }

    /**
         * Checks whether objectType is an interface correctly marked with the
         * {@link Transactional} interface, and if the provided implementation
         * is suitable for instantiation
         * 
         * @return true if it is
         */
    private boolean checkTypeValidity(final Class<?> txIface) {
	Class<?> implementationClass;

	if (!txIface.isInterface()) {
	    log.error("{} must be an interface", txIface.getSimpleName());
	    return false;
	}

	if (!txIface.isAnnotationPresent(Transactional.class)) {
	    log.error("{} is not correctly annotated with @Transactional",
		    txIface.getSimpleName());
	    return false;
	}

	implementationClass = txIface.getAnnotation(Transactional.class)
		.implementation();

	try {
	    implementationClass.getConstructor(); // gets the
	    // default
	    // constructor
	} catch (final NoSuchMethodException e) {
	    log.error("Unable to create proxy for type {}."
		    + " No default constructor available "
		    + "in its implementation", implementationClass
		    .getSimpleName(), e);
	    return false;
	} catch (final SecurityException e) {
	    log.error("Unable to create proxy for type {}."
		    + " Security exception", implementationClass
		    .getSimpleName(), e);
	    return false;
	}

	return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends Serializable> T getObject(final String label) {
	T toReturn = null;
	Iterator<TransactionalObject> it = getRootSet().outOfTxIterator();
	TransactionalObject obj = null;
	while (it.hasNext()) {
	     obj = it.next();
	    
	    if(label.equals(obj.getLabel())){
		toReturn = (T)obj;
		break;
	    }
	}
	
	if (toReturn != null) {
	    try {
		toReturn = (T) this.localCache.getObject(((TransactionalObject)toReturn).getObjectIdentifier());
	    } catch (NoSuchCachedObjectException e) {
		throw new TazioRuntimeException("Object "+((TransactionalObject)toReturn).getObjectIdentifier()+" not found in server's cache, this is really weird!");
	    }
	}
	
	return toReturn;
    }

    @Override
    public Iterator<Serializable> iterator() {
	return getRootSet().iterator();
    }

    /*
         * (non-Javadoc)
         * 
         * @see org.mentalsmash.tazio.transactions.RootSet#removeObject(java.lang.Object)
         */
    @Override
    public boolean removeObject(final Object toRemove) {
	return getRootSet().remove(toRemove);

    }

}
